traverse 是遍历 AST,并且遍历的过程中支持 visitor 的调用,在 visitor 里实现对 AST 的增删改。
我们这一节的目的是实现这样的 api:
traverse(ast, {
Identifier(node) {
node.name = 'b';
}
});
@前端进阶之旅: 代码已经复制到剪贴板
path 放到下一节实现。
# 思路分析
AST 的遍历就是树的遍历,树的遍历就深度优先、广度优先两种方式,而这里明显是深度优先遍历。
深度优先遍历要递归的遍历节点的子节点,那么我们怎么知道对象的属性是可以遍历的子节点呢?
可以维护一份数据来保存不同 AST 的什么属性是可以遍历的,然后在遍历不同节点的时候从中查找应该继续遍历什么属性。这样就实现了深度优先遍历。
在遍历的过程中可以根据类型调用不同的 visitor,然后传入当前节点。
# 代码实现
首先,我们维护这样一份数据:不同的 AST 有哪些可以遍历的属性。
const astDefinationsMap = new Map();
astDefinationsMap.set('Program', {
visitor: ['body']
});
astDefinationsMap.set('VariableDeclaration', {
visitor: ['declarations']
});
astDefinationsMap.set('VariableDeclarator', {
visitor: ['id', 'init']
});
astDefinationsMap.set('Identifier', {});
astDefinationsMap.set('NumericLiteral', {});
astDefinationsMap.set('FunctionDeclaration', {
visitor: ['id', 'params', 'body']
});
astDefinationsMap.set('BlockStatement', {
visitor: ['body']
});
astDefinationsMap.set('ReturnStatement', {
visitor: ['argument']
});
astDefinationsMap.set('BinaryExpression', {
visitor: ['left', 'right']
});
astDefinationsMap.set('ExpressionStatement', {
visitor: ['expression']
});
astDefinationsMap.set('CallExpression', {
visitor: ['callee', 'arguments']
});
@前端进阶之旅: 代码已经复制到剪贴板
然后实现递归的遍历:
function traverse(node, v